home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
C
/
Applications
/
Python 1.3.3
/
stdwin
/
Packs
/
textedit
/
textedit.c
< prev
next >
Wrap
Text File
|
1995-12-21
|
16KB
|
867 lines
/* Text Edit, high level routines */
#include "text.h"
/* Forward */
static void teinschar();
static void tesetoptdata();
static bool teoptinschar();
static void teinsert();
static void terecompute();
static int tesetstart();
static int teendofline();
static int tewordbegin();
static int tewordend();
static int _wdrawpar();
void
tereplace(tp, str)
TEXTEDIT *tp;
char *str;
{
int len= strlen(str);
if (len == 1 && teoptinschar(tp, (int) str[0]))
return;
teinsert(tp, str, len);
}
static void
teinschar(tp, c)
TEXTEDIT *tp;
int c;
{
char cbuf[2];
if (teoptinschar(tp, c))
return;
cbuf[0]= c;
cbuf[1]= EOS;
teinsert(tp, cbuf, 1);
}
/* Interfaces for wchange and wscroll that clip to the viewing rectangle */
static void
techange(tp, left, top, right, bottom)
TEXTEDIT *tp;
int left, top, right, bottom;
{
if (tp->viewing) {
CLIPMIN(left, tp->vleft);
CLIPMIN(top, tp->vtop);
CLIPMAX(right, tp->vright);
CLIPMAX(bottom, tp->vbottom);
}
wchange(tp->win, left, top, right, bottom);
}
static void
tescroll(tp, left, top, right, bottom, dh, dv)
TEXTEDIT *tp;
int left, top, right, bottom;
int dh, dv;
{
if (tp->viewing) {
CLIPMIN(left, tp->vleft);
CLIPMIN(top, tp->vtop);
CLIPMAX(right, tp->vright);
CLIPMAX(bottom, tp->vbottom);
}
wscroll(tp->win, left, top, right, bottom, dh, dv);
/* XXX Should call wchange for bits scrolled in from outside view? */
}
/* Optimization for the common case insert char.
Assumes text measurement is additive. */
static void
tesetoptdata(tp)
TEXTEDIT *tp;
{
lineno i;
bufpos k, pos, end;
zcheck();
zassert(tp->foclen == 0);
pos= zaddgap(tp->foc);
tp->opt_i= i= tewhichline(tp, pos, FALSE);
tp->opt_h= tp->left + tetextwidth(tp, tp->start[i], pos);
tp->opt_v= tp->top + i*tp->vspace;
end= tp->start[i+1];
if (end > pos && zcharbefore(end) == EOL)
zdecr(&end);
while (end > pos && zcharbefore(end) == ' ')
zdecr(&end);
for (k= pos; k < end; zincr(&k)) {
if (zcharat(k) == '\t')
break;
}
if (k < end) {
tp->opt_end=
tp->left + tetextwidth(tp, tp->start[i], znext(k));
tp->opt_avail= tp->opt_end -
(tp->left + tetextwidth(tp, tp->start[i], k));
}
else {
tp->opt_end= tp->right;
tp->opt_avail= tp->width - tetextwidth(tp, tp->start[i], end);
}
if (tp->start[i] > 0 && zcharbefore(tp->start[i]) != EOL) {
tp->opt_in_first_word= TRUE;
for (k= tp->start[i]; k < pos; zincr(&k)) {
if (isspace(zcharat(k))) {
tp->opt_in_first_word= FALSE;
break;
}
}
}
else
tp->opt_in_first_word= FALSE;
tp->opt_valid= TRUE;
}
static bool
teoptinschar(tp, c)
TEXTEDIT *tp;
int c;
{
int w;
if (tp->foclen != 0 || c == EOL || c == '\t' || tp->focprev)
return FALSE;
if (!tp->opt_valid)
tesetoptdata(tp);
if (c == ' ' && tp->opt_in_first_word)
return FALSE;
w= wcharwidth(c);
if (w >= tp->opt_avail)
return FALSE;
temovegapto(tp, tp->foc);
if (tp->gaplen < 1)
tegrowgapby(tp, 1+RESERVE);
if (tp->start[tp->opt_i] == zgapend)
tp->start[tp->opt_i]= tp->gap;
++tp->gap;
--tp->gaplen;
tp->buf[tp->foc]= c;
++tp->foc;
tp->opt_avail -= w;
if (tp->active)
wnocaret(tp->win);
tescroll(tp,
tp->opt_h, tp->opt_v,
tp->opt_end, tp->opt_v + tp->vspace,
w, 0);
wbegindrawing(tp->win);
if (tp->viewing)
wcliprect(tp->vleft, tp->vtop, tp->vright, tp->vbottom);
wdrawchar(tp->opt_h, tp->opt_v, c);
wenddrawing(tp->win);
tp->opt_h += w;
if (tp->active) {
if (!tp->viewing ||
tp->vleft <= tp->opt_h &&
tp->opt_h <= tp->vright &&
tp->vtop <= tp->opt_v &&
tp->opt_v + tp->vspace <= tp->vbottom) {
wsetcaret(tp->win, tp->opt_h, tp->opt_v);
wshow(tp->win,
tp->opt_h, tp->opt_v,
tp->opt_h, tp->opt_v + tp->vspace);
}
else
wnocaret(tp->win);
}
tp->aim= tp->opt_h;
return TRUE;
}
static void
teinsert(tp, str, len)
TEXTEDIT *tp;
char *str;
int len;
{
focpos oldfoc= tp->foc;
tehidefocus(tp);
temovegapto(tp, zfocend);
tp->gap= tp->foc;
tp->gaplen += tp->foclen;
teemptygap(tp);
tp->foclen= 0;
if (tp->gaplen < len)
tegrowgapby(tp, len-tp->gaplen+RESERVE);
strncpy(tp->buf+tp->gap, str, len);
tp->gap += len;
tp->gaplen -= len;
tp->foc += len;
terecompute(tp, zaddgap(oldfoc), zaddgap(tp->foc));
}
static int lasteol; /* Optimization trick for teendofline */
static void
terecompute(tp, first, last)
TEXTEDIT *tp;
int first, last;
{
lineno i;
lineno chfirst, chlast; /* Area to pass to wchange */
lineno shift= 0; /* Lines to shift down (negative: up) */
vcoord newbottom;
tp->start[0]= zaddgap(0);
i= 2;
while (i <= tp->nlines && tp->start[i] < first)
++i;
i -= 2;
chfirst= tp->nlines;
chlast= i;
lasteol= -1;
/* TO DO: scroll up/down if inserting/deleting lines */
for (;; ++i) {
bufpos end= teendofline(tp, tp->start[i]);
bool unchanged= (i < tp->nlines && end == tp->start[i+1]);
if (!unchanged)
shift += tesetstart(tp, i+1, end, last);
if (!(unchanged && end < first)) {
if (i < chfirst)
chfirst= i;
chlast= i+1;
}
if (end >= tp->buflen) {
if (end > tp->start[i] && zcharbefore(end) == EOL)
continue;
else
break;
}
if (unchanged && end > last) {
i= tp->nlines-1;
break;
}
}
zassert(tp->nlines > i);
if (tp->drawing) {
if (shift != 0) {
lineno k= chlast;
if (shift > 0)
k -= shift;
tescroll(tp,
tp->left, tp->top + k*tp->vspace,
tp->right, tp->top + tp->nlines*tp->vspace,
0, shift*tp->vspace);
}
techange(tp,
tp->left, tp->top + chfirst*tp->vspace,
tp->right, tp->top + chlast*tp->vspace);
}
tp->nlines= i+1;
newbottom= tp->top + tp->vspace*tp->nlines;
if (newbottom < tp->bottom)
techange(tp,
tp->left, newbottom, tp->right, tp->bottom);
tp->bottom= newbottom;
tp->aim= UNDEF;
tp->focprev= FALSE;
if (tp->drawing)
tesetcaret(tp);
zcheck();
}
static int
tesetstart(tp, i, pos, last)
register TEXTEDIT *tp;
register lineno i;
bufpos pos, last;
{
if (i > tp->nlines) {
tp->nlines= i;
if (tp->nlines >= tp->nstart) {
tp->nstart= tp->nlines + STARTINCR;
tp->start= (bufpos*)zrealloc((char*)tp->start,
tp->nstart*sizeof(int));
}
tp->start[i]= pos;
return 0;
}
else {
lineno shift= 0;
lineno k;
for (k= i; k < tp->nlines; ++k) {
if (tp->start[k] > pos)
break;
}
shift= k-1 - i;
/* start[k] should really be start[i+1] */
if (shift < 0 && tp->start[k] >= last) { /* Insert one */
++tp->nlines;
if (tp->nlines >= tp->nstart) {
tp->nstart= tp->nlines + STARTINCR;
tp->start= (int*)zrealloc((char*)tp->start,
tp->nstart*sizeof(int));
}
for (k= tp->nlines; k > i; --k)
tp->start[k]= tp->start[k-1];
}
else if (shift > 0 && pos >= last) { /* Delete some */
for (; k <= tp->nlines; ++k)
tp->start[k-shift]= tp->start[k];
tp->nlines -= shift;
if (tp->nlines < tp->nstart - STARTINCR) {
tp->nstart= tp->nlines+1;
tp->start= (int*)zrealloc((char*)tp->start,
tp->nstart*sizeof(int));
}
}
else
shift= 0; /* Don't shift (yet) */
tp->start[i]= pos;
return -shift;
}
}
static int
teendofline(tp, pos)
TEXTEDIT *tp;
bufpos pos;
{
bufpos end= tp->buflen;
bufpos k;
/* Find first EOL if any */
if (lasteol >= pos)
k= lasteol;
else {
for (k= pos; k < end && zcharat(k) != EOL; zincr(&k))
;
lasteol= k;
}
end= tetextbreak(tp, pos, k, tp->width);
/* Extend with any spaces immediately following end */
for (; end < tp->buflen && zcharat(end) == ' '; zincr(&end))
;
if (end < tp->buflen) {
/* Extend with immediately following EOL */
if (zcharat(end) == EOL)
zincr(&end);
else {
/* Search back for space before last word */
for (k= end; zdecr(&k) >= pos && !isspace(zcharat(k)); )
;
if (k >= pos)
end= znext(k);
}
}
/* Each line must be at least one character long,
otherwise a very narrow text-edit box would cause
the size calculation to last forever */
if (end == pos && end < tp->buflen)
zincr(&end);
return end;
}
bool
teevent(tp, e)
TEXTEDIT *tp;
EVENT *e;
{
if (e->window != tp->win)
return FALSE;
switch (e->type) {
case WE_CHAR:
teinschar(tp, e->u.character);
break;
case WE_COMMAND:
switch (e->u.command) {
case WC_BACKSPACE:
tebackspace(tp);
break;
case WC_RETURN:
teinschar(tp, EOL);
break;
case WC_TAB:
teinschar(tp, '\t');
break;
case WC_LEFT:
case WC_RIGHT:
case WC_UP:
case WC_DOWN:
tearrow(tp, e->u.command);
break;
default:
return FALSE;
}
break;
case WE_MOUSE_DOWN:
{
int h= e->u.where.h, v= e->u.where.v;
if (tp->viewing) {
if (h < tp->vleft || h > tp->vright ||
v < tp->vtop || v > tp->vbottom)
return FALSE;
}
else {
if (h < tp->left || h > tp->right ||
v < tp->top || v > tp->bottom)
return FALSE;
}
teclicknew(tp, h, v,
e->u.where.button == 3 ||
(e->u.where.mask & WM_SHIFT),
e->u.where.clicks > 1);
}
break;
case WE_MOUSE_MOVE:
case WE_MOUSE_UP:
if (!tp->mdown)
return FALSE;
teclicknew(tp, e->u.where.h, e->u.where.v, TRUE, tp->dclick);
if (e->type == WE_MOUSE_UP)
tp->mdown= FALSE;
break;
case WE_DRAW:
wbegindrawing(tp->win);
tedrawnew(tp, e->u.area.left, e->u.area.top,
e->u.area.right, e->u.area.bottom);
wenddrawing(tp->win);
break;
default:
return FALSE;
}
/* If broke out of switch: */
return TRUE;
}
void
tearrow(tp, code)
TEXTEDIT *tp;
int code;
{
lineno i;
bufpos pos;
tehidefocus(tp);
switch (code) {
case WC_LEFT:
if (tp->foclen != 0)
tp->foclen= 0;
else {
if (tp->foc > 0)
--tp->foc;
else
wfleep();
}
tp->aim= UNDEF;
tp->focprev= FALSE;
break;
case WC_RIGHT:
if (tp->foclen != 0) {
tp->foc += tp->foclen;
tp->foclen= 0;
}
else {
if (tp->foc < tp->buflen-tp->gaplen)
++tp->foc;
else
wfleep();
}
tp->aim= UNDEF;
tp->focprev= FALSE;
break;
/* TO DO: merge the following two cases */
case WC_UP:
if (tp->foclen > 0)
tp->foclen= 0;
else {
pos= zaddgap(tp->foc);
i= tewhichline(tp, pos, (bool) tp->focprev);
if (i <= 0)
wfleep();
else {
if (tp->aim == UNDEF)
tp->aim= tp->left + tetextwidth(tp,
tp->start[i], pos);
--i;
pos= tetextround(tp, i, tp->aim);
tp->foc= zsubgap(pos);
tp->focprev= (pos == tp->start[i+1]);
}
}
break;
case WC_DOWN:
if (tp->foclen > 0) {
tp->foc += tp->foclen;
tp->foclen= 0;
}
else {
pos= zaddgap(tp->foc);
i= tewhichline(tp, pos, (bool) tp->focprev);
if (i+1 >= tp->nlines)
wfleep();
else {
if (tp->aim == UNDEF)
tp->aim= tp->left + tetextwidth(tp,
tp->start[i], pos);
++i;
pos= tetextround(tp, i, tp->aim);
tp->foc= zsubgap(pos);
tp->focprev= (pos == tp->start[i+1]);
}
}
break;
default:
dprintf("tearrow: bad code %d", code);
break;
}
tesetcaret(tp);
}
void
tebackspace(tp)
TEXTEDIT *tp;
{
if (tp->foclen == 0) {
if (tp->foc == 0) {
wfleep();
return;
}
--tp->foc;
tp->foclen= 1;
}
teinsert(tp, "", 0);
}
bool
teclicknew(tp, h, v, extend, dclick)
TEXTEDIT *tp;
coord h, v;
bool extend, dclick;
{
lineno i;
bufpos pos;
focpos f;
tp->dclick= dclick;
pos= tewhereis(tp, h, v, &i);
f= zsubgap(pos);
if (extend) {
if (!tp->mdown) {
tp->mdown= TRUE;
if (f - tp->foc < tp->foc + tp->foclen - f)
tp->anchor= tp->foc + tp->foclen;
else
tp->anchor= tp->foc;
tp->anchor2= tp->anchor;
}
if (f >= tp->anchor) {
if (dclick)
f= tewordend(tp, f);
techangefocus(tp, tp->anchor, f);
}
else {
if (dclick)
f= tewordbegin(tp, f);
techangefocus(tp, f, tp->anchor2);
}
}
else {
tp->mdown= TRUE;
tp->anchor= tp->anchor2= f;
if (dclick) {
tp->anchor= tewordbegin(tp, tp->anchor);
tp->anchor2= tewordend(tp, f);
}
techangefocus(tp, tp->anchor, tp->anchor2);
}
tp->aim= UNDEF;
tp->focprev= (tp->foclen == 0 && pos == tp->start[i+1]);
tesetcaret(tp);
return TRUE;
}
/* Return f, 'rounded down' to a word begin */
static int
tewordbegin(tp, f)
TEXTEDIT *tp;
int f;
{
f= zaddgap(f);
for (;;) {
if (f == 0 || isspace(zcharbefore(f)))
break;
zdecr(&f);
}
return zsubgap(f);
}
/* Ditto to word end */
static int
tewordend(tp, f)
TEXTEDIT *tp;
int f;
{
f= zaddgap(f);
for (;;) {
if (f >= tp->buflen || isspace(zcharat(f)))
break;
zincr(&f);
}
return zsubgap(f);
}
int
tegetleft(tp)
TEXTEDIT *tp;
{
return tp->left;
}
int
tegettop(tp)
TEXTEDIT *tp;
{
return tp->top;
}
int
tegetright(tp)
TEXTEDIT *tp;
{
return tp->right;
}
int
tegetbottom(tp)
TEXTEDIT *tp;
{
return tp->bottom;
}
void
temove(tp, left, top, width)
TEXTEDIT *tp;
coord left, top, width;
{
temovenew(tp, left, top, left+width, top + tp->nlines*tp->vspace);
}
/*ARGSUSED*/
void
temovenew(tp, left, top, right, bottom)
TEXTEDIT *tp;
int left, top, right, bottom;
{
int oldheight= tp->bottom - tp->top;
tp->left= left;
tp->top= top;
tp->right= right;
tp->bottom= tp->top + oldheight;
if (right - left != tp->width) {
tp->width= right - left;
tp->nlines= 0; /* Experimental! */
terecompute(tp, 0, tp->buflen);
}
}
void
tesetview(tp, left, top, right, bottom)
TEXTEDIT *tp;
int left, top, right, bottom;
{
tp->viewing= TRUE;
tp->vleft= left;
tp->vtop= top;
tp->vright= right;
tp->vbottom= bottom;
}
void
tenoview(tp)
TEXTEDIT *tp;
{
tp->viewing= FALSE;
}
void
tesetfocus(tp, foc1, foc2)
TEXTEDIT *tp;
focpos foc1, foc2;
{
if (foc1 > tp->buflen - tp->gaplen)
foc1= tp->buflen - tp->gaplen;
if (foc2 > tp->buflen - tp->gaplen)
foc2= tp->buflen - tp->gaplen;
if (foc1 < 0)
foc1= 0;
if (foc2 < foc1)
foc2= foc1;
techangefocus(tp, foc1, foc2);
tp->aim= UNDEF;
tp->focprev= FALSE;
tesetcaret(tp);
}
int
tegetfoc1(tp)
TEXTEDIT *tp;
{
return tp->foc;
}
int
tegetfoc2(tp)
TEXTEDIT *tp;
{
return tp->foc + tp->foclen;
}
int
tegetnlines(tp)
TEXTEDIT *tp;
{
return tp->nlines;
}
char *
tegettext(tp)
TEXTEDIT *tp;
{
temovegapto(tp, tp->buflen - tp->gaplen);
if (tp->gaplen < 1)
tegrowgapby(tp, 1+RESERVE);
tp->buf[tp->gap]= EOS;
return tp->buf;
}
int
tegetlen(tp)
TEXTEDIT *tp;
{
return tp->buflen - tp->gaplen;
}
void
tesetbuf(tp, buf, buflen)
TEXTEDIT *tp;
char *buf;
int buflen;
{
bool drawing= tp->drawing;
if (buf == NULL || buflen < 0)
return;
if (drawing)
techange(tp, tp->left, tp->top, tp->right, tp->bottom);
free(tp->buf);
tp->buf= buf;
tp->buflen= buflen;
tp->foc= tp->foclen= tp->gap= tp->gaplen= 0;
tp->drawing= FALSE;
terecompute(tp, 0, tp->buflen);
if (drawing) {
tp->drawing= TRUE;
techange(tp, tp->left, tp->top, tp->right, tp->bottom);
}
}
/* The following paragraph-drawing routines are experimental.
They cannibalize on the existing text-edit code,
which makes it trivial to ensure the lay-out is the same,
but causes overhead to initialize a text-edit struct.
The flag 'drawing' has been added to the textedit struct,
which suppresses calls to tesetcaret, wchange and wscroll
from tesetup and terecompute.
It doesn't suppress actual drawing, which only occurs when
tedraw is called.
Note -- this could be optimized, but it is infrequently,
so I don't care until I get complaints. */
/* Draw a paragraph of text, exactly like tedraw would draw it.
Parameters are the top left corner, the width, the text and its length.
Return value is the v coordinate of the bottom line.
An empty string is drawn as one blank line. */
int
wdrawpar(left, top, text, width)
int left, top;
char *text;
int width;
{
return _wdrawpar(left, top, text, width, TRUE);
}
/* Measure the height of a paragraph of text, when drawn with wdrawpar. */
int
wparheight(text, width)
char *text;
int width;
{
return _wdrawpar(0, 0, text, width, FALSE);
}
/* Routine to do the dirty work for the above two.
Size calculations are implemented by going through the normal
routine but suppressing the actual drawing. */
static int
_wdrawpar(left, top, text, width, draw)
int left, top;
char *text;
int width;
bool draw;
{
TEXTEDIT *tp= tesetup((WINDOW*)NULL, left, top, left+width, top, FALSE);
int v;
tesetbuf(tp, text, strlen(text));
if (draw)
tedraw(tp);
v= tegetbottom(tp);
tp->buf= NULL;
tefree(tp);
return v;
}